KM.ShadersManager = function() {};

KM.LOG = false;

// Differents shaders
KM.ShadersManager.shaderPrograms = {};
KM.ShadersManager.shaderProgramsNb = 0;

// Current shader program
KM.ShadersManager.currentShaderProgram = null;

KM.ShadersManager.loadShaders = function( gl, shaderDescriptions, callback ) {
    var shaderDescriptionsNb = shaderDescriptions.length;
    
    // For each shaders description, a shader program is built
    for( var i = 0; i < shaderDescriptionsNb; i++ ) {
        var shaderDescription = shaderDescriptions[ i ];
        
        var shader = KM.ShadersManager.loadShader( gl, shaderDescription );
        
        if( !shader ) {
            if( KM.LOG ) if( KM.LOG ) console.error( "ShadersManager.js,loadShaders() : Can't load shader " + i + ", name = " + shaderDescription.name);
            continue;
        }
        
        KM.ShadersManager.currentShaderProgram = shader;
        KM.ShadersManager.shaderPrograms[ shaderDescription.name ] = shader;
        KM.ShadersManager.shaderProgramsNb++;
        
        if( KM.LOG ) if( KM.LOG ) console.log("ShadersManager.js, loadShaders() : " + shader._name + " has been loaded ( " + ( i + 1 ) + " / " + shaderDescriptionsNb + " )");
    }
    
    callback( KM.ShadersManager.shaderProgramsNb );
};

KM.ShadersManager.loadShader = function( gl, shaderDescription ) {
    // VS
    var vertexShader = KM.ShadersManager.buildShader( gl, gl.VERTEX_SHADER, shaderDescription.vsContent );
    if( !vertexShader ) {
        if( KM.LOG ) console.error( "ShadersManager.js,loadShader() : Can't build vs " + shaderDescription.name);
        return null;
    }
    
    // FS
    var fragmentShader = KM.ShadersManager.buildShader( gl, gl.FRAGMENT_SHADER, shaderDescription.fsContent );
    if( !fragmentShader ) {
        if( KM.LOG ) console.error( "ShadersManager.js,loadShader() : Can't build fs " + shaderDescription.name);
        return null;
    }
    
    // Shader program
    var shaderProgram = gl.createProgram();
    gl.attachShader( shaderProgram, vertexShader );
    gl.attachShader( shaderProgram, fragmentShader );
    gl.linkProgram( shaderProgram );
    
    if( !gl.getProgramParameter( shaderProgram, gl.LINK_STATUS ) ) {
        return null;
    }

    // Vertices positions attribute
    shaderProgram._vertexPositionAttribute = gl.getAttribLocation( shaderProgram, "aVertexPosition" );
    gl.enableVertexAttribArray( shaderProgram._vertexPositionAttribute );
    
    // Vertices normals attribute
    if( shaderDescription.isVertexNormalAttribute ) {
        shaderProgram._vertexNormalAttribute = gl.getAttribLocation( shaderProgram, "aVertexNormal" );
        gl.enableVertexAttribArray( shaderProgram._vertexNormalAttribute ); 
    }
    
//    // Vertices textures attributes
//    if( shaderDescription.isTextureCoordAttribute ) {
//        // Vertices texture coords attribute
//        shaderProgram.textureCoordAttribute = gl.getAttribLocation( shaderProgram, "aTextureCoord" );
//        gl.enableVertexAttribArray( shaderProgram.textureCoordAttribute );    
//        
//        // Uniform texture
//        shaderProgram.samplerUniform = gl.getUniformLocation( shaderProgram, "uSampler");
//    }
    
    // Projection matrix
    shaderProgram._pMatrixUniform = gl.getUniformLocation( shaderProgram, "uPMatrix" );
    
    // ModelView matrix
    shaderProgram._mvMatrixUniform = gl.getUniformLocation( shaderProgram, "uMVMatrix" );
    
    // View matrix
    shaderProgram._viewMatrixUniform = gl.getUniformLocation( shaderProgram, "uViewMatrix" );
    
    // Lightings
    if( shaderDescription.isLightings ) {
        shaderProgram._isLightings = true;
        shaderProgram._nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix");
        shaderProgram._ambientColorUniform = gl.getUniformLocation( shaderProgram, "uAmbientColor" );
        shaderProgram._directionalLightColorUniform = gl.getUniformLocation( shaderProgram, "uDirectionalLightColor" );        
        shaderProgram._directionalLightDirectionUniform = gl.getUniformLocation( shaderProgram, "uDirectionalLightDirection" );
        shaderProgram._KeUniform = gl.getUniformLocation( shaderProgram, "uKe" );        
        shaderProgram._KaUniform = gl.getUniformLocation( shaderProgram, "uKa" );        
        shaderProgram._KdUniform = gl.getUniformLocation( shaderProgram, "uKd" );        
        shaderProgram._KsUniform = gl.getUniformLocation( shaderProgram, "uKs" );        
        shaderProgram._AlphaUniform = gl.getUniformLocation( shaderProgram, "uAlpha" );        
    }
    
    shaderProgram._name = shaderDescription.name;

    return shaderProgram;
};

KM.ShadersManager.buildShader = function( gl, shaderType, shaderContent ) {
    var shader = gl.createShader( shaderType );

    gl.shaderSource( shader, shaderContent );
    gl.compileShader( shader );

    if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) {
        console.error( gl.getShaderInfoLog( shader ) );
        return null;
    }

    return shader;
};

KM.ShadersManager.getShaderProgram = function( shaderDescriptionName )
{
    return KM.ShadersManager.shaderPrograms[ shaderDescriptionName ];
};

KM.ShadersManager.bindShader = function( gl, nextShaderProgram )
{
    // Current shader program attributes
    var currentShaderProgramAttributesNb = gl.getProgramParameter( KM.ShadersManager.currentShaderProgram, gl.ACTIVE_ATTRIBUTES );
    if( KM.LOG ) console.log("ShadersManager.js, bindShader() : current shader " + KM.ShadersManager.currentShaderProgram._name + " has " + currentShaderProgramAttributesNb + " attributes.");
    
    // Next shader program attributes
    var nextShaderProgramAttributesNb = gl.getProgramParameter( nextShaderProgram, gl.ACTIVE_ATTRIBUTES );
    if( KM.LOG ) console.log("ShadersManager.js, bindShader() : next shader " + nextShaderProgram._name + " has " + nextShaderProgramAttributesNb + " attributes.");
    
    // More attributes than the current shader program => enabling new attributes
    if( nextShaderProgramAttributesNb > currentShaderProgramAttributesNb )
    {
        if( KM.LOG ) console.log("ShadersManager.js, bindShader() : Enabling " + ( nextShaderProgramAttributesNb - currentShaderProgramAttributesNb ) + " new attributes.");
        for( var i = currentShaderProgramAttributesNb; i < nextShaderProgramAttributesNb; i++)
        {
            gl.enableVertexAttribArray( i );
        }        
    }
    // Less attributes than the current shader program => disabling useless attributes
    else if( nextShaderProgramAttributesNb < currentShaderProgramAttributesNb )
    {
        if( KM.LOG ) console.log("ShadersManager.js, bindShader() : Disabling " + ( currentShaderProgramAttributesNb - nextShaderProgramAttributesNb ) + " useless attributes.");
        for( var i = nextShaderProgramAttributesNb; i < currentShaderProgramAttributesNb; i++ )
        {
            gl.disableVertexAttribArray( i );
        }      
    }
    else
    {
        if( KM.LOG ) console.log("ShadersManager.js, bindShader() : Same attributes number, no need to enabling / disabling.")
    }
    
    KM.ShadersManager.currentShaderProgram = nextShaderProgram;
    gl.useProgram( KM.ShadersManager.currentShaderProgram );
};

KM.ShadersManager.unbindShader = function( gl )
{
    gl.useProgram( null );
};